﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using CLIFileRW;
using System.Diagnostics;

namespace ILMatcher_OLD
{
    public struct EvaluationStep
    {
        int automataPosition;
        KeyValuePair<long, long> currentPossibleMatch;

        public EvaluationStep(int automataPosition, KeyValuePair<long, long> currentPossibleMatch){
            this.automataPosition = automataPosition;
            this.currentPossibleMatch = currentPossibleMatch;
        }

        public KeyValuePair<long, long> PossibleMatch
        {
            get { return currentPossibleMatch; }
        }

        public int AutomataPosition
        {
            get { return automataPosition; }
        }
    }

    public class Query
    {
        private MethodInfo methodQuery;
        private List<MatchNode> nodes;

        public Query(MethodInfo m)
        {
            if (null == m) throw new ArgumentNullException("m");
             methodQuery = m;
        }

        public void Compile()
        {
            //todo: completare il set di istruzioni possibile

            nodes = new List<MatchNode>();

            MethodTableCursor cur = CLIFile.FindMethod(methodQuery);
            CLIFileRW.MethodBody cb = cur.MethodBody;
            LocalsVarSig locals = cb.LocalsSignature;

            CLIType[] vars = locals.GetVariables().ToArray<CLIType>();
            CLIType[] args = cur.GetMethodSignature().GetParameters(cur.FindCLIType()).ToArray<CLIType>();

            ILCursor ilc = cb.ILInstructions;
            ilc.TrackStack(args, vars, cb.MaxStack);
            ilc.TrackTargets();

            bool[] varIsDelegate = new bool[vars.Length];
            bool[] argIsDelegate = new bool[args.Length];

            //check if args or locals variables are delegates and set to true in support bool arrays
            for(int i=0; i<vars.Length; i++)
                varIsDelegate[i] = vars[i].GetReflectionType().IsSubclassOf(typeof(Delegate));
            for (int i = 0; i < args.Length; i++)
                argIsDelegate[i] = args[i].GetReflectionType().IsSubclassOf(typeof(Delegate));

            MatchNode n = null;   //a che mi serviva?

            nodes = new List<MatchNode>();

            while (ilc.Next())
            {
                ILInstruction instr = ilc.Instr;

                switch ((int)instr.op.Value)
                {
                    case (int) OPCODES.Nop:
                        break;
                    case (int) OPCODES.Ret:
                        break;
                    case (int) OPCODES.Stloc_0:
                        MatchNode mn = new StoreMatchNode(ilc, 0);
                        //todo: menage bind tables
                        nodes.Add(mn);
                        break;
                    case (int) OPCODES.Ldc_I4:
                        throw new NotImplementedException();
                        break;
                    case (int) OPCODES.Ldc_I4_0:
                        throw new NotImplementedException();
                        break;
                    case (int) OPCODES.Ldc_I4_1:
                        throw new NotImplementedException();
                        break;
                    case (int) OPCODES.Ldc_I4_2:
                        nodes.Add(new LoadConstMatchNode(ilc));
                        break;
                    case (int) OPCODES.Ldarg:
                        //n = MakeLoadMatchNode(0); fixme
                        throw new NotImplementedException();
                        break;
                    case (int) OPCODES.Ldarg_1:
                        if (argIsDelegate[1])
                        {
                            Type returnedType = ((CLIType)args[1]).GetReflectionType().GetMethod("Invoke").ReturnType;
                            if (returnedType == typeof(void))
                            {
                                //add KleeneStarNode
                                throw new NotImplementedException();
                            }
                            else
                            {
                                //add expMatchNode
                                nodes.Add(new ExpressionMatchNode(ilc, returnedType));
                            }
                        }
                        else
                        {
                            throw new NotImplementedException();
                            //fixme vedere come gestire il binding!
                        }
                        break;
                    case (int) OPCODES.Callvirt:
                        throw new NotImplementedException();
                        break;
                    default:
                        nodes.Add(new ExactMatchNode(ilc));
                        break;
                }
                //if is a load
                    //check if delegate -> make it a proper kleene node
                    //else update binding table (da rivedere questa cosa)
                //else make it proper exact match (controlla per store e simili) forse è meglio avere uno switch  
            }
            nodes.ToString();
        }

        public bool Match(MethodInfo m)
        {

            MethodTableCursor cur = CLIFile.FindMethod(m);
            CLIFileRW.MethodBody cb = cur.MethodBody;
            LocalsVarSig locals = cb.LocalsSignature;

            CLIType[] vars = locals.GetVariables().ToArray<CLIType>();
            CLIType[] args = cur.GetMethodSignature().GetParameters(cur.FindCLIType()).ToArray<CLIType>();

            ILCursor ilc = cb.ILInstructions;
            ilc.TrackStack(args, vars, cb.MaxStack);
            ilc.TrackTargets();

            int currentNode = 0;

            Stack<EvaluationStep> evaluationStack = new Stack<EvaluationStep>();
            evaluationStack.Push(new EvaluationStep(0, new KeyValuePair<long, long>(ilc.Position, ilc.Position)));
            ilc.PushState();
            ilc.PushState();

            List<object> matchesFound = new List<object>(); //fixme fare in modo che faccia parte dell'oggetto e che restituisca risultati utilizzabili :P

            bool exit = false;
            do
            {
                if(evaluationStack.Count > 0){
                    EvaluationStep currentEvaluationStep = evaluationStack.Pop();
                    ilc.PopState(); //<-- mi fa errore ???

                    while (ilc.Position < currentEvaluationStep.PossibleMatch.Value)
                        ilc.Next();
                    if (currentEvaluationStep.AutomataPosition == nodes.Count)
                    {
                        //addMatch!
                        matchesFound.Add(currentEvaluationStep.PossibleMatch);
                    }
                    else
                    {
                        //se il nodo dell'automa trova almeno un match aggiungi i possibili match alla pila
                        if (nodes[currentEvaluationStep.AutomataPosition].Match(ilc))
                        {
                            while (nodes[currentEvaluationStep.AutomataPosition].PossibleMatches.Count > 0)
                            {

                                evaluationStack.Push(new EvaluationStep(1 + currentEvaluationStep.AutomataPosition,
                                                                        new KeyValuePair<long, long>(currentEvaluationStep.PossibleMatch.Key,
                                                                                                    nodes[currentEvaluationStep.AutomataPosition].PossibleMatches[0].FinalPosition)));

                                ilc.PushState();
                                nodes[currentEvaluationStep.AutomataPosition].PossibleMatches.RemoveAt(0);
                            }
                        }
                        //else
                        //{ //devo far avanzare il cursore o uscire
                        //    //exit = !PushState(ilc, evaluationStack);
                        //    //ilc.PopState(); No Altrimenti finisce che faccio due pop consecutive per ogni fallimento
                        //}
                    }
                }
                else
                {//devo far avanzare il cursore o uscire
                    exit = !PushState(ilc, evaluationStack);
                }
            } while (!exit);

            return matchesFound.Count > 0;

            #region OldCode
            //while (ilc.Next() && currentNode != nodes.Count - 1)
            //{
            //    if (nodes[currentNode].CodeSection(ilc))
            //        switch (nodes[currentNode].NodeType)
            //        {
            //            //case MatchNodeType.ExactMatch:
            //            case MatchNodeType.Expression: //gestire le operazioni da fare per procedere al passo successivo (ad esempio i vari path di soluzione)
            //                ExpressionMatchNode emn = (ExpressionMatchNode)nodes[currentNode];
            //                long begginningPosition = ilc.Position;
            //                ilc.PushState(); //save stateBeforeMatch, to restore after match failure
            //                if (emn.PossibleMatches.Count > 0)
            //                {
            //                    //todo: gestire i vari path
            //                    CodeSection currentPM = emn.PossibleMatches[0];
            //                    emn.PossibleMatches.RemoveAt(0);

            //                    while (ilc.Position < currentPM.FinalPosition)
            //                        ilc.Next();

            //                    ilc.PushState();

            //                }
            //                break;
            //            //case MatchNodeType.KleeneStar:
            //            //case MatchNodeType.Load:
            //            //case MatchNodeType.LoadConst:
            //            //case MatchNodeType.Store:
            //            default:

            //                currentNode++;
            //                break;
            //        }
            //    else
            //    {
            //        //ripristina lo stato precedente e prosegui con il match
            //        ilc.PopState();
            //        currentNode--;
            //    }

            //}

            //return ++currentNode == nodes.Count; 
            #endregion
        }

        private bool PushState(ILCursor ilc, Stack<EvaluationStep> evalStack)
        {
            if (ilc.Next())
            {
                ilc.PushState();
                evalStack.Push(new EvaluationStep(0, new KeyValuePair<long, long>(ilc.Position, ilc.Position)));
                return true;
            }
            return false;
        }

        #region Properties

        public MethodInfo MethodQuery
        {
            get { return methodQuery; }
        }


        #endregion

    }

    public enum MatchNodeType
    {
        ExactMatch,
        Expression,
        KleeneStar,
        Load,
        Store,
        LoadConst
    }


    //il match deve mettere i possibili bind in una variabile di istanza poi presa dal chiamante (magari con un check (has bind)
    public class MatchNode
    {
        private MatchNodeType nodeType;
        private bool hasPossibleMatches;
        private List<PossibleMatch> possibleMatches; //fixme mettere private


        public MatchNode(MatchNodeType type, ILCursor ilc) //fixme
        {
            nodeType = type;
            possibleMatches = new List<PossibleMatch>();
        }

        #region Properties

        public List<PossibleMatch> PossibleMatches
        {
            get { return possibleMatches; }
        }

        public bool HasPossibleMatches
        {
            get { return possibleMatches.Count > 0; }
            set { hasPossibleMatches = value; }
        }

        public MatchNodeType NodeType
        {
            get { return nodeType; }
        }
        #endregion

        #region Methods

        public virtual bool Match(ILCursor ilc)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

    public class ExpressionMatchNode : MatchNode
    {
        private Type returnType;
        //private List<CodeSection> possibleMatches; //fixme mettere private

        public ExpressionMatchNode(ILCursor ilc, Type returnType, bool isArg=true) 
            : base(MatchNodeType.Expression, ilc)
        {
            if (ilc == null || returnType == null) throw new ArgumentNullException("parameters");
            this.returnType = returnType;
            ilc.Next();
        }

        #region Properties
        public Type ReturnType
        {
            get { return returnType; }
        }

       

        //public bool HasPossibleMatches
        //{
        //    get { return possibleMatches.Count > 0; }
        //    set { base.HasPossibleMatches = value; }
        //}

        #endregion


        #region Methods

        public override bool Match(ILCursor ilc)
        {
            ilc.PushState();
            //skip unuseful instructions
            while ((ilc.Instr.op.Value == (int)OPCODES.Nop) || (ilc.Instr.op.Value == (int)OPCODES.Ret))
                ilc.Next();

            Type prevType = null;
            List<KeyValuePair<long, Type>> ssList = null;

            //prepare support Dictionary to find possible matches (gain informations on stack height and returned type before the actual instruction read
            List<KeyValuePair<long, Type>> stackSizes = new List<KeyValuePair<long, Type>>();

            //automatonPosition
            long initialPosition = ilc.Position;
            int ss = ilc.OperandStack.Count;
            do
            {
               if(ilc.OperandStack.Count == ss)
                stackSizes.Add(new KeyValuePair<long, Type>(ilc.Position, prevType));
               prevType = (ilc.OperandStack.Count > 0) ? ilc.OperandStack[0].Type.GetReflectionType() : null;
            } while (ilc.Next());


            
            for (int i = 0; i < stackSizes.Count; i++)
                if (stackSizes[i].Value != null && stackSizes[i].Value.Equals(ReturnType))
                    PossibleMatches.Add(new PossibleMatch(ss, initialPosition, stackSizes[i].Key)); //il controllo sul tipo precedente deve essere qui

            ilc.PopState();
            //todo: ripulire un po' gli oggetti creati o ci pensa lui stesso all'uscita del metodo?
            return PossibleMatches.Count > 0;

            #region OldCode
            ////skip unuseful instructions
            //while ((ilc.Instr.op.Value == (int)OPCODES.Nop) || (ilc.Instr.op.Value == (int)OPCODES.Ret))
            //    ilc.Next();

            //Type prevType = null;
            //List<KeyValuePair<long, Type>> ssList = null;

            //ilc.PushState(); //fixme: controllare che serva a qualcosa altrimenti eliminarlo :P

            ////prepare support Dictionary to find possible matches (gain informations on stack height and returned type before the actual instruction read
            //Dictionary<int, List<KeyValuePair<long, Type>>> stackSizes = new Dictionary<int, List<KeyValuePair<long, Type>>>();
            //do
            //{
            //    int ss = ilc.OperandStack.Count;

            //    if (!stackSizes.ContainsKey(ss))
            //    {
            //        List<KeyValuePair<long, Type>> temp = new List<KeyValuePair<long, Type>>();
            //        stackSizes.Add(ilc.OperandStack.Count, temp);
            //        temp.Add(new KeyValuePair<long, Type>(ilc.Position, null));
            //    }
            //    else
            //    {
            //        if (stackSizes.TryGetValue(ss, out ssList))
            //            ssList.Add(new KeyValuePair<long, Type>(ilc.Position, prevType));
            //        else
            //            throw new ArgumentException("list error");
            //    }

            //    prevType = (ilc.OperandStack.Count > 0) ? ilc.OperandStack[0].Type.GetReflectionType() : null;
            //} while (ilc.Next()) ;


            //possibleMatches = new List<CodeSection>();

            //foreach (int key in stackSizes.Keys)
            //{
            //    for(int x = 0; x<stackSizes[key].Count; x++)
            //        for (int i = x+1; i < stackSizes[key].Count; i++)
            //            if(stackSizes[key][i].Value != null && stackSizes[key][i].Value.Equals(ReturnType))
            //                possibleMatches.Add(new CodeSection(key, stackSizes[key][x].Key, stackSizes[key][i].Key)); //il controllo sul tipo precedente deve essere qui
              
            //}
            ////todo: ripulire un po' gli oggetti creati o ci pensa lui stesso all'uscita del metodo?
            //return possibleMatches.Count > 0;

#endregion
        }

        public override string ToString()
        {
            return "" + NodeType + ": " + ReturnType.Name;
        }
        #endregion
    }


    public class PossibleMatch //fixme farla tornare struct, forse posso anche toglierla del tutto e usare un semplice keyvaluepair
    {
        private int stackSize;
        private long initialPosition;
        private long finalPosition;

        public PossibleMatch(int stackSize, long initialPosition, long finalPosition)
        {
            this.stackSize = stackSize;
            this.initialPosition = initialPosition;
            this.finalPosition = finalPosition;
        }

        public override string ToString()
        {
            return "(" + initialPosition + ", " + finalPosition + ")";
        }

        public long InitialPosition
        {
            get { return initialPosition; }
        }

        public long FinalPosition
        {
            get { return finalPosition; }
        }
    }

    public class ExactMatchNode : MatchNode
    {
        ILInstruction instr;

        public ExactMatchNode(ILCursor ilc) :
            base(MatchNodeType.ExactMatch, ilc)
        {
            instr = ilc.Instr;
        }

        #region Methods
        public override bool Match(ILCursor ilc)
        {
            bool isMatch = ilc.Instr.op.Name.Equals(instr.op.Name);
            if (isMatch)
            {
                PossibleMatches.Add(new PossibleMatch(ilc.OperandStack.Count, ilc.Position, ilc.Position));
            }

            return isMatch;
        } 

        public override string ToString()
        {
            return "" + NodeType + ": " + instr.op.Name;
        }

        #endregion
    }

    public class LoadConstMatchNode : MatchNode 
    {
        private object parameter;

        public LoadConstMatchNode(ILCursor ilc)
            : base(MatchNodeType.LoadConst, ilc)
        {
            parameter = ILInstruction.Normalize(ilc.Instr).ResolveParameter(ilc.FileReader);
        }

        #region Properities
        public object Parameter
        {
            get { return parameter; }
        }
        #endregion

        #region Methods
        public override bool Match(ILCursor ilc)
        {
            bool isMatch = isLdc(ilc.Instr) && ILInstruction.Normalize(ilc.Instr).ResolveParameter(ilc.FileReader).Equals(parameter); //fixme aggiungere il binding??? forse no in questo caso;
            if (isMatch)
            {
                PossibleMatches.Add(new PossibleMatch(ilc.OperandStack.Count, ilc.Position, ilc.Position));
            }
            return isMatch;
        }

        private bool isLdc(ILInstruction instr)
        {
            return instr.op.Value == (int)OPCODES.Ldc_I4_2; //fixme aggiungere le altre lodc oppure fare normalize
        }

        public override string ToString()
        {
            return "" + NodeType + ": ldc " + parameter.ToString();
        }
        #endregion
    }

    public class StoreMatchNode : MatchNode
    {
        int storePosition;

        public StoreMatchNode(ILCursor ilc, int position)
            : base(MatchNodeType.Store, ilc)
        {
            storePosition = position;
        }

        #region Properties
        public int StorePosition
        {
            get { return storePosition; }
        }
        #endregion

        #region Methods

        public override bool Match(ILCursor ilc)
        {
            bool isMatch = isStore(ilc.Instr); //fixme: aggiungere il binding
            if (isMatch)
            {
                PossibleMatches.Add(new PossibleMatch(ilc.OperandStack.Count, ilc.Position, ilc.Position));
            }

            return isMatch;
        }

        public override string ToString()
        {
            return "" + NodeType + ": " + 1;
        }

        private bool isStore(ILInstruction instr)
        {
           return (instr.op.Value == (int)OPCODES.Stloc_0) || (instr.op.Value == (int) OPCODES.Stloc_1); //fixme aggiungere le altre store
        }
        #endregion
    }
   
}


